#include <linux/module.h>
#include <linux/moduleparam.h>
#include "common.h"
#include "init.h"
#include "timer.h"
#include "worker_thread.h"
#include "fifo.h"
#include "task.h"
#include "mock.h"
#include "serial_file.h"
#include "trigger.h"
#include "neli.h"
#include "pedal.h"
#include "sync_source.h"

MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.1");
MODULE_AUTHOR("bravomikekilo@buaa.edu.cn");

static const int KFIFO_SIZE = 4096 * 16;

static char *serial_file_path = "/var/log/sync-counter/serial";
module_param(serial_file_path, charp, 0444);
MODULE_PARM_DESC(serial_file_path, "path for serial file");

static char *base_directory = "/storage/sync-counter";

static struct file *serial_file = NULL;
static atomic_t serial = ATOMIC_INIT(0);

static struct kfifo *fifo = NULL;

module_param(base_directory, charp, 0444);
MODULE_PARM_DESC(base_directory, "base directory for record");

static bool use_mock = false;
module_param(use_mock, bool, 0444);

static char *mock_serial = "/dev/ttyACM0";
module_param(mock_serial, charp, 0444);
MODULE_PARM_DESC(mock_serial, "mock serial device file");

// whether userspace app linked with trigger file, init to false
static atomic_t app_linked = ATOMIC_INIT(0);


static struct file *open_serial_file(const char *filename);

// called with userspace app open trigger file
static void app_on(void *data)
{
	PR_INFO("app linked");
	atomic_set_release(&app_linked, 1);
}

// called with userspace app close trigger file
static void app_off(void *data)
{
	PR_INFO("app unlinked");
	atomic_set_release(&app_linked, 0);
}

static void start_source(void) {
	if (use_mock) {
		PR_INFO("start mock source");
		start_mock_source();
	} else {
		PR_INFO("start sync source");
		start_sync_source();
	}
}

static void stop_source(void) {
	if (use_mock) {
		PR_INFO("stop mock source");
		stop_mock_source();
	} else {
		PR_INFO("stop sync source");
		stop_sync_source();
	}
}


static int netlink_start(struct sk_buff *skb, struct genl_info *info)
{
	PR_INFO("netlink start called");
	start_source();
	return 0;
}

static int netlink_stop(struct sk_buff *skb, struct genl_info *info)
{
	PR_INFO("netlink stop called");
	stop_source();
	return 0;
}


static void trigger_start(void *data)
{
	int ret = 0;
	uint32_t new_serial = 0;
	uint32_t next_serial = 0;
	loff_t pos = 0;
	struct sync_counter_frame frame;

	PR_INFO("trigger start triggered");

	if (!atomic_read_acquire(&app_linked)) {
		PR_WARN("trigger stepped without applinked");
		return;
	}

	new_serial = atomic_fetch_inc_acquire(&serial);
	next_serial = new_serial + 1;
	PR_INFO("serialize serial to serial_file");
	ret = kernel_write(serial_file, &next_serial, sizeof(uint32_t), &pos);
	if (ret != sizeof(uint32_t)) {
		PR_ERR("write record file failed");
		return;
	}
	frame.count = TRIGGER_COUNT;
	frame.time_ns = new_serial;

	ret = kfifo_in(fifo, &frame, sizeof(struct sync_counter_frame));
	if (ret == 0) {
		PR_ERR("trigger start write kfifo failed, kfifo full");
		return;
	}

	PR_INFO("trigger START triggered, serial %u", new_serial);
	ret = boardcast_signal_start(new_serial);
	PR_INFO("genl boardcast start ret: %d", ret);
}

static void trigger_stop(void *data)
{
	int ret = 0;

	PR_INFO("trigger stop triggered");

	if (!atomic_read_acquire(&app_linked)) {
		PR_WARN("trigger stepped without applinked");
		return;
	}


	PR_INFO("trigger STOP triggered");
	ret = boardcast_signal_stop();
	PR_INFO("genl boardcast stop ret: %d", ret);
	stop_source();
}

int init_module(void)
{
	char name_buffer[256];
	// struct kfifo *fifo = NULL;
	int ret = 0;
	uint32_t serial_init = 0;
	loff_t pos = 0;

	struct log_to_file_config task_cfg = {
		.base_path = base_directory,
		.sleep_msecs = 200,
	};

	PR_INFO("sync-counter module init");
	PR_INFO("use mock: %d", use_mock);
	PR_INFO("base directory: %s", base_directory);


	PR_INFO("begin init system setup");
	setup_init();
	PR_INFO("init system setup finish");

	// setup serial file
	// sprintf(name_buffer, "%s/serial", base_directory);
	serial_file = open_serial_file(serial_file_path);
	if (IS_ERR(serial_file)) {
		PR_ERR("open serial file failed, errno: %ld", PTR_ERR(serial_file));
		return PTR_ERR(serial_file);
	}

	ret = kernel_read(serial_file, &serial_init, sizeof(uint32_t), &pos);
	if (pos != sizeof(uint32_t)) {
		PR_ERR("read serial from serial file failed, errno %d", ret);
		return ret;
	}
	PR_INFO("current record serial is %u", serial_init);
	atomic_set(&serial, serial_init);

	PR_INFO("begin init kfifo");
	ret = new_kfifo(KFIFO_SIZE, "frame-fifo", &fifo);
	if (ret) {
		PR_INFO("init kfifo failed, errno %d", ret);
		return ret;
	}

	if (fifo == NULL) {
		PR_INFO("init kfifo failed, return null");
		return -1;
	}
	PR_INFO("finish init kfifo");
	task_cfg.fifo = fifo;

	PR_INFO("begin init worker thread");
	ret = new_worker_thread(log_kfifo_to_file_task, "log_kfifo2file", &task_cfg); 
	if (ret) {
		PR_INFO("init worker_thread failed, return null");
		return ret;
	}
	PR_INFO("finish init worker thread");

	PR_INFO("init app trigger");
	// init userspace application trigger
	ret = new_trigger("user-lock", app_on, app_off, NULL);
	if (ret) {
		PR_ERR("init app trigger failed, error code: %d", ret);
		return ret;
	}
	PR_INFO("finish app trigger");

	// begin setup mock source or real source
	if (use_mock) {
		PR_INFO("begin init mock source");

		ret = setup_mock_source(fifo, mock_serial);
		if (ret) {
			PR_ERR("init mock source failed");
			return ret;
		}
		PR_INFO("finish setup mock source");
		PR_INFO("begin init mock trigger");
		ret = new_trigger("mock-pedal", trigger_start, trigger_stop, NULL);
		if (ret) {
			PR_INFO("init mock trigger failed, errno: %d", ret);
			return ret;
		}
	} else {
		PR_INFO("begin init sync source");

		ret = setup_sync_source(fifo);
		if (ret) {
			PR_ERR("init sync source failed");	
			return ret;
		}

		PR_INFO("begin init pedal trigger");
		ret = setup_pedal_trigger(trigger_start, trigger_stop, NULL);
		if (ret) {
			PR_ERR("init pedal trigger failed");
			return ret;
		}
	}

	// begin setup netlink
	PR_INFO("setup netlink");
	ret = setup_netlink(
		"sync-counter", 
		netlink_start,
		netlink_stop
	);
	if (ret) {
		PR_ERR("setup netlink family failed, errno: %d", ret);
		return ret;
	}

	PR_INFO("init finish");
	return ret;
}

void cleanup_module(void)
{
	PR_INFO("sync-counter begin CLEANUP!");
	cleanup();
	PR_INFO("sync-counter finish CLEANUP!");
	
	if (serial_file != NULL) {
		filp_close(serial_file, current->files);
		serial_file = NULL;
	}
	return;
}

struct file *open_serial_file(const char *filename)
{
	struct file *f = NULL;
	uint32_t count = 0;
	ssize_t ret = 0;
	loff_t pos = 0;
	f = filp_open(filename, O_CREAT | O_RDWR, 0644);
	if (IS_ERR(f)) {
		PR_INFO("open serial file failed, errno %ld", PTR_ERR(f));
		return f;
	}

	ret = kernel_read(f, &count, sizeof(uint32_t), &pos);
	if (ret < 0) {
		PR_ERR("kernel read serial file failed, errno %ld", ret);
		filp_close(f, current->files);
		return ERR_PTR(ret);
	}
	
	if (ret == 0) {
		PR_INFO("serial file is empty, initalize it");
		ret = kernel_write(f, &count, sizeof(uint32_t), &pos);
		if (ret < 0) {
			PR_ERR("write serial file failed, errno %ld", ret);
			filp_close(f, current->files);
			return ERR_PTR(ret);
		}
	}

	return f;
}
